package panda.query.condition;

import panda.query.scan.Scanner;
import panda.query.struct.Attribute;
import panda.query.tree.QueryTreeNode;
import panda.record.Schema;
import panda.transaction.Transaction;

public class Relation implements BooleanExpression{
	
	public final static int EQ = 0, NEQ = 1, LT = 2, GT = 3, LTE = 4, GTE = 5;
	 
	public ValueExpression l, r;
	public int rel;
	
	/**
	 * 
	 * @param l
	 * @param r
	 * @param rel
	 */
	public Relation(ValueExpression l, ValueExpression r, int rel) {
		this.l = l;
		this.r = r;
		this.rel = rel;
	}
	
	
	public int isSatisfied(Scanner s, Transaction tx) {
		int res = l.evaluate(s, tx).compareTo(r.evaluate(s, tx));
		
		switch (rel) {
		case EQ:
			if (Math.abs(res) == Integer.MAX_VALUE)
				return 0;
			if (res == 0)
				return 1;
			return -1;
		case NEQ: 
			if (Math.abs(res) == Integer.MAX_VALUE)
				return 0;
			if (res != 0)
				return 1;
			return -1;
		case LT:
			if (Math.abs(res) == Integer.MAX_VALUE)
				return 0;
			if (res < 0)
				return 1;
			return -1;
		case GT:
			if (Math.abs(res) == Integer.MAX_VALUE)
				return 0;
			if (res > 0)
				return 1;
			return -1;
		case LTE:
			if (Math.abs(res) == Integer.MAX_VALUE)
				return 0;
			if (res <= 0)
				return 1;
			return -1;
		case GTE:
			if (Math.abs(res) == Integer.MAX_VALUE)
				return 0;
			if (res >= 0)
				return 1;
			return -1;
		}
		return -1;
	}

	public int equateWithConstant(Attribute attr) {
		if (rel == EQ && (l.dominates(attr) != r.dominates(attr)))
			return 1;
		return 0;
	}

	public double reductionFactor(QueryTreeNode n) {
		if (rel == EQ && l instanceof AttributeName && r instanceof AttributeName) {
			AttributeName l = (AttributeName)this.l;
			AttributeName r = (AttributeName)this.r;
			return 1.0/Math.max(n.distinctSize(l.getAttribute()), n.distinctSize(r.getAttribute()));
		}
		if (rel == NEQ && l instanceof AttributeName && r instanceof AttributeName) {
			AttributeName l = (AttributeName)this.l;
			AttributeName r = (AttributeName)this.r;
			return 1 - Math.min(n.distinctSize(l.getAttribute()), n.distinctSize(r.getAttribute()));
		}
		if (rel == EQ && l instanceof AttributeName && r.isConstant()) {
			AttributeName l = (AttributeName)this.l;
			
			return 1.0/n.distinctSize(l.getAttribute());
			
		}
		if (rel == NEQ && l instanceof AttributeName && r.isConstant()) {
			AttributeName l = (AttributeName)this.l;
			
			return 1 - 1.0/n.distinctSize(l.getAttribute());
		}
		if (rel == EQ && r instanceof AttributeName && l.isConstant()) {
			AttributeName r = (AttributeName)this.r;
			
			return 1.0/n.distinctSize(r.getAttribute());
		}
		if (rel == NEQ && r instanceof AttributeName && l.isConstant()) {
			AttributeName r = (AttributeName)this.r;
			
			return 1 - 1.0/n.distinctSize(r.getAttribute());			
		}
		if (l.isConstant() && r.isConstant()) {
			if (l.asConstant().compareTo(r.asConstant()) == 0)
				return 1;
			else
				return 0;
		}
		return 1;
	}
	
	/**
	 * Check whether this condition applies to the select of a table with
	 * the specified schema
	 * @param sch the given schema
	 * @return true or false
	 */
	public boolean appliesToSelect(Schema sch) {
		return l.appliesTo(sch) && r.appliesTo(sch);
	}
	
	/**
	 * Check whether this condition applies to the join of two tables with
	 * the specified schemata
	 * @param l the schema of the left table
	 * @param r the schema of the right table
	 * @return true or false
	 */
	public boolean appliesToJoin(Schema l, Schema r) {
		return rel == EQ && (this.l instanceof AttributeName) && (this.r instanceof AttributeName) &&
			((this.l.appliesTo(l) && this.r.appliesTo(r)) || (this.l.appliesTo(r) && this.r.appliesTo(l)));
	}

}
